package com.cd.university.service.abs;

import com.cd.university.common.RestResult;
import com.cd.university.common.UniversityConstants;
import com.cd.university.common.enums.MessageEnum;
import com.cd.university.common.lru.LRUCacheFactory;
import com.cd.university.common.lru.LRUHashMap;
import com.cd.university.common.lru.LRUMapFactory;
import com.cd.university.common.utils.PageUtil;
import com.cd.university.common.utils.SpringUtil;
import com.cd.university.controller.form.MessageIdForm;
import com.cd.university.db.mapper.ProjectFileMapper;
import com.cd.university.db.mapper.ProjectTeamMapper;
import com.cd.university.db.pojo.Message;
import com.cd.university.db.pojo.ProjectFile;
import com.cd.university.db.pojo.ProjectFileKey;
import com.cd.university.db.pojo.User;
import com.cd.university.service.MessageService;
import com.cd.university.vo.MessageVo;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.sql.Date;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author 陈栋
 * @create 2021/11/22 21:59
 */
@Component
public abstract class AbstractMessage implements MessageService, InitializingBean {

    @Autowired
    private ProjectTeamMapper projectTeamMapper;

    @Autowired
    private ProjectFileMapper projectFileMapper;

    /**
     * key为项目的id，value为项目的logo的url
     */
    private static HashMap<Integer, String> projectLogoMap = new HashMap<>();

    /**
     * key为用户id，value为消息集合,LinkHashMap实现LRU算法
     */
    private static LRUCacheFactory<Integer, List<Message>> projectUsersMap = null;

    @Override
    public void afterPropertiesSet() throws Exception {
        projectUsersMap = LRUMapFactory.getLRUCache("linkMap");
    }

    private static Long startTime = System.currentTimeMillis();
    private static Integer count = 0;

    @Override
    @Async
    public void teamInvitation(Message message) {
        saveMessage(message);
    }

    @Override
    @Async
    public void projectApproved(Message message) {
        saveMessage(message);
    }

    @Override
    @Async
    public void submitFinalApplication(Message message) {
        saveMessage(message);
    }

    @Override
    @Async
    public void passFinalApplication(Message message) {
        saveMessage(message);
    }

    /**
     * 把消息保存
     * @param message 消息
     * @return 保存是否成功
     */
    public boolean saveMessage(Message message) {
        // 查询与项目有关的所有的用户
        List<User> users = projectTeamMapper.searchUsersByProjectId(message.getProjectId());

        // 遍历用户，存在存储库和缓存中
        for (User user : users) {
            Integer userId = user.getUserId();
            // 查询缓存
            List<Message> messages = projectUsersMap.get(userId);
            // 如果缓存中没有,那么查询数据库
            if (messages==null) {
                messages = findMessagesByUser(userId);
                // 放入到缓存中
                projectUsersMap.put(userId, messages);
            }

            message.setUserId(userId);
            message.setMessageTime(new Date(System.currentTimeMillis()));
            message.setConfirmStatus(0);

            // 保存到存储库中
            message.setUserId(userId);
            if (saveRepository(message)) {
                // 更新、保存到缓存中
                saveCache(userId, message);
            }else {
                throw new RuntimeException("存入信息数据失败");
            }
        }
        return true;
    }

    @Override
    public RestResult approveMessage(List<MessageIdForm> messageIdList) {
        // 更新数据库
        int row = approveRepoMessages(messageIdList);
        updateCache(messageIdList);

        return RestResult.success("已读了" + row + "条消息");
    }


    @Override
    public RestResult approveAllByUserId(Integer userId) {
        // 更新数据库
        int row = approveUserMessages(userId);

        AbstractMessage bean = SpringUtil.getBean(AbstractMessage.class);
        bean.updateCache(userId);
        return RestResult.success("已读了" + row + "条消息");
    }

    /**
     * 保存到持久层中
     * @param message 消息
     * @return 是否保存成功
     */
    public abstract boolean saveRepository(Message message);

    /**
     * 从存储库中查询用户消息
     * @param userId 用户id
     * @return 消息
     */
    public abstract List<Message> findMessagesByUser(Integer userId);

    public abstract int approveRepoMessages(List<MessageIdForm> messageIdList);

    public abstract int approveUserMessages(Integer userId);

    @Async
    public void updateCache(Integer userId) {
        projectUsersMap.remove(userId);
    }

    @Async
    public void updateCache(List<MessageIdForm> messageIdList) {
        for (MessageIdForm messageId : messageIdList) {
            // 通过用户id查询用户消息集合
            List<Message> messageList = searchMessageListByUserId(messageId.getUserId());

            if (messageList==null) {
                continue;
            }
            // 遍历消息集合
            for (Message message : messageList) {
                if (message.getMessageId().equals(messageId.getMessageId())) {
                    messageList.remove(message);
                    projectUsersMap.put(messageId.getUserId(),messageList);
                    break;
                }
            }
        }
    }

    @Override
    public RestResult sendMessage(Integer userId, Integer pageNum, Integer pageSize) {
        List<Message> messages = searchMessageListByUserId(userId);

        if (messages==null) {
            return RestResult.success("该用户没有消息");
        }

        ArrayList<MessageVo> vos = new ArrayList<>();
        CopyOnWriteArrayList<Message> list = new CopyOnWriteArrayList<>(messages);
        for (Message message : list) {
            // 生成一个项目文件的主键类，用户查询项目的logo
            String projectLogo = findProjectLogo(message.getProjectId());
            if (projectLogo!=null) {
                vos.add(new MessageVo(message, getMessageInfo(message),projectLogo));
            }
        }
        PageInfo pageInfo = PageUtil.startPage(pageNum, pageSize, vos);
        return RestResult.success("用户消息如下")
                .put("data",pageInfo);
    }

    private List<Message> searchMessageListByUserId(Integer userId) {
        List<Message> messages = projectUsersMap.get(userId);
        // 如果缓存中没有，查询数据库
        if (messages==null) {

            messages = findMessagesByUser(userId);

            // 判断是否需要动态扩容
            resize();
            // 存入Cache中
            projectUsersMap.put(userId, messages);
        }
        return messages;
    }

    private void saveCache(Integer userId, Message message) {
        List<Message> list = projectUsersMap.get(userId);
        if (list==null) {
            ArrayList<Message> messages = new ArrayList<>();
            messages.add(message);
            projectUsersMap.put(userId,messages);
        }else {
            list.add(message);
            projectUsersMap.put(userId,list);
        }
    }

    private String getMessageInfo(Message message) {
        for (MessageEnum value : MessageEnum.values()) {
            if (value.getMessageStatus().equals(message.getMessageStatus())) {
                return value.getMessage();
            }
        }
        throw new RuntimeException("项目状态出错");
    }

    /**
     * 获得项目的logo的Url
     * @param projectId 项目id
     * @return 项目的url
     */
    private String findProjectLogo(Integer projectId) {
        // 先查询缓存
        if (projectLogoMap.get(projectId)!=null) {
            return projectLogoMap.get(projectId);
        }
        // 如果缓存中没有，那么查询数据库
        ProjectFileKey logo = new ProjectFileKey(projectId, "logo");
        ProjectFile projectFile = projectFileMapper.selectByPrimaryKey(logo);
        if (projectFile!=null) {
            projectLogoMap.put(projectFile.getProjectId(), projectFile.getPath());
            return projectFile.getPath();
        }else {
            return null;
        }
    }

    /**
     * 动态决定缓存的大小，策略是
     * 五分钟以内如果缓存替换超过32，那么容量扩大一倍
     */
    @Async
    public void resize() {
        // 修改计数++
        count++;
        Long endTime = null;
        if (((endTime  = System.currentTimeMillis()) -startTime)/(1000*60)>5) {
            if (count>=48) {
                synchronized (projectUsersMap) {
                    int size = projectUsersMap.size();
                    // 数组容量扩大两倍
                    projectUsersMap = projectUsersMap.modifyCapacity(size << 1);
                }
            }else {
                // 开始时间重置
                startTime = endTime;
            }
            // 修改计数器重置
            count = 0;
        }
    }
}
